// Copyright 2006-2008 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// The infrastructure used for (localized) message reporting in V8.
//
// Note: there's a big unresolved issue about ownership of the data
// structures used by this framework.

#ifndef V8_MESSAGES_H_
#define V8_MESSAGES_H_

#include <memory>

#include "src/handles.h"
#include "src/message-template.h"

namespace v8 {
namespace internal {
    namespace wasm {
        class WasmCode;
    }

    // Forward declarations.
    class AbstractCode;
    class FrameArray;
    class IncrementalStringBuilder;
    class JSMessageObject;
    class LookupIterator;
    class SharedFunctionInfo;
    class SourceInfo;
    class WasmInstanceObject;

    class V8_EXPORT_PRIVATE MessageLocation {
    public:
        MessageLocation(Handle<Script> script, int start_pos, int end_pos);
        MessageLocation(Handle<Script> script, int start_pos, int end_pos,
            Handle<SharedFunctionInfo> shared);
        MessageLocation();

        Handle<Script> script() const { return script_; }
        int start_pos() const { return start_pos_; }
        int end_pos() const { return end_pos_; }
        Handle<SharedFunctionInfo> shared() const { return shared_; }

    private:
        Handle<Script> script_;
        int start_pos_;
        int end_pos_;
        Handle<SharedFunctionInfo> shared_;
    };

    class StackFrameBase {
    public:
        virtual ~StackFrameBase() = default;

        virtual Handle<Object> GetReceiver() const = 0;
        virtual Handle<Object> GetFunction() const = 0;

        virtual Handle<Object> GetFileName() = 0;
        virtual Handle<Object> GetFunctionName() = 0;
        virtual Handle<Object> GetScriptNameOrSourceUrl() = 0;
        virtual Handle<Object> GetMethodName() = 0;
        virtual Handle<Object> GetTypeName() = 0;
        virtual Handle<Object> GetEvalOrigin();

        // Returns the script ID if one is attached, -1 otherwise.
        int GetScriptId() const;

        virtual int GetPosition() const = 0;
        // Return 1-based line number, including line offset.
        virtual int GetLineNumber() = 0;
        // Return 1-based column number, including column offset if first line.
        virtual int GetColumnNumber() = 0;

        // Returns index for Promise.all() async frames, or -1 for other frames.
        virtual int GetPromiseIndex() const = 0;

        virtual bool IsNative() = 0;
        virtual bool IsToplevel() = 0;
        virtual bool IsEval();
        virtual bool IsAsync() const = 0;
        virtual bool IsPromiseAll() const = 0;
        virtual bool IsConstructor() = 0;
        virtual bool IsStrict() const = 0;

        MaybeHandle<String> ToString();
        virtual void ToString(IncrementalStringBuilder& builder) = 0;

        // Used to signal that the requested field is unknown.
        static const int kNone = -1;

    protected:
        StackFrameBase() = default;
        explicit StackFrameBase(Isolate* isolate)
            : isolate_(isolate)
        {
        }
        Isolate* isolate_;

    private:
        virtual bool HasScript() const = 0;
        virtual Handle<Script> GetScript() const = 0;
    };

    class JSStackFrame : public StackFrameBase {
    public:
        JSStackFrame(Isolate* isolate, Handle<Object> receiver,
            Handle<JSFunction> function, Handle<AbstractCode> code,
            int offset);
        ~JSStackFrame() override = default;

        Handle<Object> GetReceiver() const override { return receiver_; }
        Handle<Object> GetFunction() const override;

        Handle<Object> GetFileName() override;
        Handle<Object> GetFunctionName() override;
        Handle<Object> GetScriptNameOrSourceUrl() override;
        Handle<Object> GetMethodName() override;
        Handle<Object> GetTypeName() override;

        int GetPosition() const override;
        int GetLineNumber() override;
        int GetColumnNumber() override;

        int GetPromiseIndex() const override;

        bool IsNative() override;
        bool IsToplevel() override;
        bool IsAsync() const override { return is_async_; }
        bool IsPromiseAll() const override { return is_promise_all_; }
        bool IsConstructor() override { return is_constructor_; }
        bool IsStrict() const override { return is_strict_; }

        void ToString(IncrementalStringBuilder& builder) override;

    private:
        JSStackFrame() = default;
        void FromFrameArray(Isolate* isolate, Handle<FrameArray> array, int frame_ix);

        bool HasScript() const override;
        Handle<Script> GetScript() const override;

        Handle<Object> receiver_;
        Handle<JSFunction> function_;
        Handle<AbstractCode> code_;
        int offset_;

        bool is_async_ : 1;
        bool is_constructor_ : 1;
        bool is_promise_all_ : 1;
        bool is_strict_ : 1;

        friend class FrameArrayIterator;
    };

    class WasmStackFrame : public StackFrameBase {
    public:
        ~WasmStackFrame() override = default;

        Handle<Object> GetReceiver() const override;
        Handle<Object> GetFunction() const override;

        Handle<Object> GetFileName() override { return Null(); }
        Handle<Object> GetFunctionName() override;
        Handle<Object> GetScriptNameOrSourceUrl() override { return Null(); }
        Handle<Object> GetMethodName() override { return Null(); }
        Handle<Object> GetTypeName() override { return Null(); }

        int GetPosition() const override;
        int GetLineNumber() override { return wasm_func_index_; }
        int GetColumnNumber() override { return kNone; }

        int GetPromiseIndex() const override { return kNone; }

        bool IsNative() override { return false; }
        bool IsToplevel() override { return false; }
        bool IsAsync() const override { return false; }
        bool IsPromiseAll() const override { return false; }
        bool IsConstructor() override { return false; }
        bool IsStrict() const override { return false; }
        bool IsInterpreted() const { return code_ == nullptr; }

        void ToString(IncrementalStringBuilder& builder) override;

    protected:
        Handle<Object> Null() const;

        bool HasScript() const override;
        Handle<Script> GetScript() const override;

        Handle<WasmInstanceObject> wasm_instance_;
        uint32_t wasm_func_index_;
        wasm::WasmCode* code_; // null for interpreted frames.
        int offset_;

    private:
        WasmStackFrame() = default;
        void FromFrameArray(Isolate* isolate, Handle<FrameArray> array, int frame_ix);

        friend class FrameArrayIterator;
        friend class AsmJsWasmStackFrame;
    };

    class AsmJsWasmStackFrame : public WasmStackFrame {
    public:
        ~AsmJsWasmStackFrame() override = default;

        Handle<Object> GetReceiver() const override;
        Handle<Object> GetFunction() const override;

        Handle<Object> GetFileName() override;
        Handle<Object> GetScriptNameOrSourceUrl() override;

        int GetPosition() const override;
        int GetLineNumber() override;
        int GetColumnNumber() override;

        void ToString(IncrementalStringBuilder& builder) override;

    private:
        friend class FrameArrayIterator;
        AsmJsWasmStackFrame() = default;
        void FromFrameArray(Isolate* isolate, Handle<FrameArray> array, int frame_ix);

        bool is_at_number_conversion_;
    };

    class FrameArrayIterator {
    public:
        FrameArrayIterator(Isolate* isolate, Handle<FrameArray> array,
            int frame_ix = 0);

        StackFrameBase* Frame();

        bool HasFrame() const;
        void Advance();

    private:
        Isolate* isolate_;

        Handle<FrameArray> array_;
        int frame_ix_;

        WasmStackFrame wasm_frame_;
        AsmJsWasmStackFrame asm_wasm_frame_;
        JSStackFrame js_frame_;
    };

    // Determines how stack trace collection skips frames.
    enum FrameSkipMode {
        // Unconditionally skips the first frame. Used e.g. when the Error constructor
        // is called, in which case the first frame is always a BUILTIN_EXIT frame.
        SKIP_FIRST,
        // Skip all frames until a specified caller function is seen.
        SKIP_UNTIL_SEEN,
        SKIP_NONE,
    };

    class ErrorUtils : public AllStatic {
    public:
        static MaybeHandle<Object> Construct(
            Isolate* isolate, Handle<JSFunction> target, Handle<Object> new_target,
            Handle<Object> message, FrameSkipMode mode, Handle<Object> caller,
            bool suppress_detailed_trace);

        static MaybeHandle<String> ToString(Isolate* isolate, Handle<Object> recv);

        static MaybeHandle<Object> MakeGenericError(
            Isolate* isolate, Handle<JSFunction> constructor, MessageTemplate index,
            Handle<Object> arg0, Handle<Object> arg1, Handle<Object> arg2,
            FrameSkipMode mode);

        // Formats a textual stack trace from the given structured stack trace.
        // Note that this can call arbitrary JS code through Error.prepareStackTrace.
        static MaybeHandle<Object> FormatStackTrace(Isolate* isolate,
            Handle<JSObject> error,
            Handle<Object> stack_trace);
    };

    class MessageFormatter {
    public:
        static const char* TemplateString(MessageTemplate index);

        V8_EXPORT_PRIVATE static MaybeHandle<String> Format(Isolate* isolate,
            MessageTemplate index,
            Handle<String> arg0,
            Handle<String> arg1,
            Handle<String> arg2);

        static Handle<String> Format(Isolate* isolate, MessageTemplate index,
            Handle<Object> arg);
    };

    // A message handler is a convenience interface for accessing the list
    // of message listeners registered in an environment
    class MessageHandler {
    public:
        // Returns a message object for the API to use.
        V8_EXPORT_PRIVATE static Handle<JSMessageObject> MakeMessageObject(
            Isolate* isolate, MessageTemplate type, const MessageLocation* location,
            Handle<Object> argument, Handle<FixedArray> stack_frames);

        // Report a formatted message (needs JS allocation).
        V8_EXPORT_PRIVATE static void ReportMessage(Isolate* isolate,
            const MessageLocation* loc,
            Handle<JSMessageObject> message);

        static void DefaultMessageReport(Isolate* isolate, const MessageLocation* loc,
            Handle<Object> message_obj);
        static Handle<String> GetMessage(Isolate* isolate, Handle<Object> data);
        static std::unique_ptr<char[]> GetLocalizedMessage(Isolate* isolate,
            Handle<Object> data);

    private:
        static void ReportMessageNoExceptions(Isolate* isolate,
            const MessageLocation* loc,
            Handle<Object> message_obj,
            v8::Local<v8::Value> api_exception_obj);
    };

} // namespace internal
} // namespace v8

#endif // V8_MESSAGES_H_
